package net.jcip.examples;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

/**
 * ProducerConsumer
 * <p/>
 * Producer and consumer tasks in a desktop search application
 * 
 * @author Brian Goetz and Tim Peierls
 */
public class ProducerConsumer {

	static List<BlockingDeque<File>> taskDeques = new ArrayList<>();

	static class FileCrawler implements Runnable {
		private final FileFilter fileFilter;
		private final File root;
		private int counter = 0;

		public FileCrawler(final FileFilter fileFilter, File root) {
			this.root = root;
			this.fileFilter = new FileFilter() {
				public boolean accept(File f) {
					return f.isDirectory() || fileFilter.accept(f);
				}
			};
		}

		private boolean alreadyIndexed(File f) {
			return false;
		}

		public void run() {
			try {
				crawl(root);
			} catch (InterruptedException e) {
				Thread.currentThread().interrupt();
			}
		}

		private void crawl(File root) throws InterruptedException {
			File[] entries = root.listFiles(fileFilter);
			if (entries != null) {
				for (File entry : entries)
					if (entry.isDirectory())
						crawl(entry);
					else if (!alreadyIndexed(entry)) {
						counter++;
						if (counter >= taskDeques.size()) {
							counter = 0;
						}
						BlockingDeque<File> fileDeque = taskDeques.get(counter);
						fileDeque.put(entry);
					}
			}
		}
	}

	static class Indexer implements Runnable {
		private int index;
		private volatile boolean isRunning = true;
		private int stolen = 0;
		private int total = 0;
		
		public Indexer(int index) {
			this.index = index;
		}

		public void run() {
			while (true) {
					try {
						BlockingDeque<File> fileDqeue = taskDeques.get(this.index);
						File file = fileDqeue.poll(1, TimeUnit.MILLISECONDS);
						if (file == null) {
							for (int i = 0; i < taskDeques.size(); i++) {
								if (i != index) {
									file = taskDeques.get(i).pollLast(1, TimeUnit.MILLISECONDS);
									if (file != null) {
										stolen++;
										break;
									}
								}
							}
						} 
						
						if (file != null) {
							indexFile(file);
						} else if (!isRunning) {
							break;
						}
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
			}
			System.out.printf("Index: %d, Total: %d, Stolen: %d, Percent: %.1f%%%n", index, total, stolen, 100d * stolen / total);
		}

		public void indexFile(File file) {
			total++;
			
		}

		public void stop() {
			isRunning = false;
		};
	}

	private static final int BOUND = 10;
	private static final int N_CONSUMERS = 10;
	private static Indexer[] indexers = new Indexer[N_CONSUMERS];
	private static FileCrawler[] crawlers;
	
	public static void startIndexing(File[] roots) {
		
		for (int i = 0; i < N_CONSUMERS; i++) {
			BlockingDeque<File> queue = new LinkedBlockingDeque<>(10);
			taskDeques.add(queue);
		}

		FileFilter filter = new FileFilter() {
			public boolean accept(File file) {
				return true;
			}
		};

		crawlers = new FileCrawler[roots.length];
		for (int i = 0; i < roots.length; i++) {
			crawlers[i] = new FileCrawler(filter, roots[i]);
			new Thread(crawlers[i]).start();
		}

		for (int i = 0; i < N_CONSUMERS; i++) {
			indexers[i] = new Indexer(i);
			new Thread(indexers[i]).start();
		}
	}
	
	public static void stopIndexing() {
		for (Indexer indexer : indexers) {
			indexer.stop();
		}
	}

	public static void main(String[] args) {
		startIndexing(new File("C:\\eclipse").listFiles());
		stopIndexing();
	}
}